package com.astruyk.chordophone;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Align;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.View;

/**
 * A view that displays a fretboard with fingering and a label for a chord.
 * 
 * @author Anton Struyk
 */
public class ChordView extends View {
	private static final int NUMBER_OF_STRINGS = 4;
	private static final int FRETS_TO_DRAW = 7;
	private static final int FRET_SPACING = 40;
	private static final int FRETBOARD_PADDING = 12;
	private static final int HALF_OPEN_STRING_LABEL_SIZE = 5;
	private static final int OPEN_STRING_LABEL_OFFSET_FROM_FRETBOARD = 5;
		
	private Paint _fretboardTopPaint;
	private Paint _fretboardPaint;
	private Paint _stringPaint;
	private Paint _fingeringPaint;
	private Paint _mutedFingeringPaint;
	private Paint _openFingeringPaint;
	private Paint _labelPaint;
	
	private boolean _showLabel;
	private Chord _chord;
	
	public ChordView(Context context) {
		super(context);
		
		initialize();
	}

	public ChordView(Context context, AttributeSet attrs) {
		super(context, attrs);

		initialize();
	}

	public ChordView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		
		initialize();
	}
	
	private void initialize(){
		_fretboardTopPaint = new Paint();
		_fretboardTopPaint.setAntiAlias(true);
		_fretboardTopPaint.setColor(Color.WHITE);
		_fretboardTopPaint.setStrokeWidth(5);
		_fretboardTopPaint.setStrokeCap(Cap.ROUND);
		
		_fretboardPaint = new Paint();
		_fretboardPaint.setAntiAlias(true);
		_fretboardPaint.setColor(Color.WHITE);
		_fretboardPaint.setStrokeWidth(3);
		
		_fingeringPaint = new Paint();
		_fingeringPaint.setAntiAlias(true);
		_fingeringPaint.setColor(Color.BLUE);
		
		_stringPaint = new Paint();
		_stringPaint.setAntiAlias(true);
		_stringPaint.setStrokeWidth(2);
		_stringPaint.setStrokeCap(Cap.ROUND);
		_stringPaint.setStyle(Style.STROKE);
		_stringPaint.setColor(Color.GRAY);
		
		_openFingeringPaint = new Paint();
		_openFingeringPaint.setAntiAlias(true);
		_openFingeringPaint.setStyle(Style.STROKE);
		_openFingeringPaint.setStrokeWidth(2);
		_openFingeringPaint.setColor(Color.WHITE);
		
		_mutedFingeringPaint = new Paint();
		_mutedFingeringPaint.setAntiAlias(true);
		_mutedFingeringPaint.setStrokeWidth(3);
		_mutedFingeringPaint.setStrokeCap(Cap.ROUND);
		_mutedFingeringPaint.setColor(Color.RED);
		
		_labelPaint = new Paint();
		_labelPaint.setAntiAlias(true);
		_labelPaint.setColor(Color.WHITE);
		_labelPaint.setTextAlign(Align.CENTER);
		_labelPaint.setTextSize(14);
		_labelPaint.setFakeBoldText(true); 
	}
	
	/**
	 * Sets this control to display the indicated chord.
	 * @param chord the chord to display in this view
	 */
	public void setChord(Chord chord){
		_chord = chord;
		invalidate();
	}
	
	/**
	 * Sets whether or not the name of the chord should be displayed
	 * at the top of the view.
	 * @param showLabel true if the label should be shown, false otherwise
	 */
	public void setShowLabel(boolean showLabel){
		_showLabel = showLabel;
	}
	
	@Override
	public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(
				measureWidth(widthMeasureSpec),
				measureHeight(heightMeasureSpec));
	}
	
	private int measureWidth(int widthMeasureSpec) {
		int specMode = MeasureSpec.getMode(widthMeasureSpec);
		int specSize = MeasureSpec.getSize(widthMeasureSpec);
		
		if(specMode == MeasureSpec.EXACTLY){
			return specSize;
		}
		int result = 100;
		
		if(specMode == MeasureSpec.AT_MOST){
			return Math.min(specSize, result);
		}
		return result;
	}
	
	private int measureHeight(int heightMeasureSpec) {
		int specMode = MeasureSpec.getMode(heightMeasureSpec);
		int specSize = MeasureSpec.getSize(heightMeasureSpec);
		
		if(specMode == MeasureSpec.EXACTLY){
			return specSize;
		}
		int result = (FRETS_TO_DRAW * FRET_SPACING)
					+ (HALF_OPEN_STRING_LABEL_SIZE * 2)
					+ OPEN_STRING_LABEL_OFFSET_FROM_FRETBOARD
					+ (_showLabel ? 100 : 0)
					+ 33;
		
		if(specMode == MeasureSpec.AT_MOST){
			return Math.min(specSize, result);
		}
		return result;
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		final int width = getWidth();
		
		//Figure out where the top of the fretboard is going to be, since
		//it's used to draw stuff
		int fretboardTopY = (HALF_OPEN_STRING_LABEL_SIZE * 2)
							+ OPEN_STRING_LABEL_OFFSET_FROM_FRETBOARD
							+ 3;
		String label = (_chord == null) ? "No Chord" : _chord.getLabelString();
		int[] fingering = (_chord == null) ? new int[NUMBER_OF_STRINGS] : _chord.getFingering();
		
		if(true){
			//Draw the label at the top of the screen in the center.
			Rect bounds = new Rect();
			_labelPaint.getTextBounds(label, 0, label.length(), bounds);
			canvas.drawText(
					label,
					(width / 2),
					 bounds.height(),
					_labelPaint);
			fretboardTopY += bounds.height() + OPEN_STRING_LABEL_OFFSET_FROM_FRETBOARD;
		}
		
		final int stringSpacing = (width - (2 * FRETBOARD_PADDING)) / (NUMBER_OF_STRINGS - 1);
		
		//Draw the horizontal lines for the fretboard
		for(int i = 1; i < FRETS_TO_DRAW + 1; i++){
			final int y = fretboardTopY + (i * FRET_SPACING);
			canvas.drawLine(
					FRETBOARD_PADDING,
					y,
					width - FRETBOARD_PADDING,
					y,
					_fretboardPaint);
		}
		
		//Draw the strings over the fretboard
		for(int i = 0; i < NUMBER_OF_STRINGS; i++){
			final int x = getXForString(i, stringSpacing);
			canvas.drawLine(
					x,
					fretboardTopY,
					x,
					fretboardTopY + (FRETS_TO_DRAW * FRET_SPACING),
					_stringPaint);
		}
		
		//Draw the top of the fretboard
		canvas.drawLine(
				FRETBOARD_PADDING,
				fretboardTopY,
				width - FRETBOARD_PADDING,
				fretboardTopY,
				_fretboardTopPaint);
		
		//Draw the fingering
		final int fingeringSize = (stringSpacing / 2) - 1;
		for(int noteString = 0; noteString < NUMBER_OF_STRINGS; noteString++){
			int noteFret = fingering[noteString];
			int noteX = getXForString(noteString, stringSpacing);
			
			if(noteFret == 0 || noteFret == -1){
				int y = fretboardTopY - (HALF_OPEN_STRING_LABEL_SIZE + OPEN_STRING_LABEL_OFFSET_FROM_FRETBOARD);
				if(noteFret == 0){
					//Draw an 'O' at the top of the string, it's supposed to be open.
					canvas.drawCircle(noteX, y, HALF_OPEN_STRING_LABEL_SIZE, _openFingeringPaint);
				}else{
					//Draw an X at the top of the string, it is supposed to be muted.
					canvas.drawLine(
							noteX - HALF_OPEN_STRING_LABEL_SIZE,
							y - HALF_OPEN_STRING_LABEL_SIZE,
							noteX + HALF_OPEN_STRING_LABEL_SIZE,
							y + HALF_OPEN_STRING_LABEL_SIZE,
							_mutedFingeringPaint);
					canvas.drawLine(
							noteX - HALF_OPEN_STRING_LABEL_SIZE,
							y + HALF_OPEN_STRING_LABEL_SIZE,
							noteX + HALF_OPEN_STRING_LABEL_SIZE,
							y - HALF_OPEN_STRING_LABEL_SIZE,
							_mutedFingeringPaint);
				}
			}else{
				//Draw a circle over the indicated fret
				//The y-coordinate is the 
				int noteY = fretboardTopY + (FRET_SPACING * noteFret) - (FRET_SPACING / 2);
				canvas.drawCircle(
						noteX,
						noteY,
						fingeringSize, //-1 so the edges of circles beside eachother don't touch
						_fingeringPaint);
			}
		}
	}
	
	private int getXForString(int stringNumber, int stringSpacing){
		return FRETBOARD_PADDING + (stringNumber * stringSpacing);
	}
}
